﻿#define _CRT_SECURE_NO_WARNINGS
#include"snake.h"

//控制台屏幕大小
#define CONCOLE_COLS 150
#define CONCOLE_LINES 45  //必须要大于10--->Game_Star_Screen
//游戏地图大小
#define MAP_COLS 70
#define MAP_LINES 35
//初始化时蛇头的位置
#define INIT_COLS1 12
#define INIT_LINES1 10
//皮肤
#define WALL_CLOTH L'□'
#define SNAKE1_CLOTH L'■'
#define FOOD_CLOTH L'★'

#define KEY_PASS(pv) ((GetAsyncKeyState(pv) & 1) ? 1 : 0)
/***********************************************************************************************/
/*********************************游戏功能辅助函数--间接实现************************************/
/***********************************************************************************************/

//游戏环境设置
void GameSet()
{
	//获取设备信息
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//设置窗口
	system("mode con cols=150 lines=45");
	system("title 贪吃蛇");
	//环境本地化的适配
	setlocale(LC_ALL, "");
	//隐藏光标
	CONSOLE_CURSOR_INFO curinfo = { 0 };
	GetConsoleCursorInfo(houtput, &curinfo);
	curinfo.bVisible = false;
	SetConsoleCursorInfo(houtput, &curinfo);
}

//光标位置设置
void SetPos(int x, int y)
{
	//获取设备信息
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD coord = { x,y };
	//移动光标到该位置
	SetConsoleCursorPosition(houtput, coord);
}

//游戏开始界面
void Game_Star_Screen()
{
	SetPos(CONCOLE_COLS / 2 - 10, CONCOLE_LINES / 2);
	printf("欢迎来到贪吃蛇小游戏");
	SetPos(CONCOLE_COLS / 2 - 7, CONCOLE_LINES / 2 + 5);
	system("pause");
	system("cls");
}

//游戏介绍（模式选择）界面
void Game_Introduction()
{
	SetPos(CONCOLE_COLS / 2 - 33, CONCOLE_LINES / 2);
	wprintf(L"用↑↓→←来控制蛇的移动，F1为加速，F2为减速，加速可获得更高的得分");
	SetPos(CONCOLE_COLS / 2 - 7, CONCOLE_LINES / 2 + 5);
	system("pause");
	system("cls");
	return 0;
}

//创建地图
void CreatMap(int cols, int lines)
{
	int i = 0;
	//上
	SetPos(0, 0);
	for (i = 0; i < cols; i += 2)
		wprintf(L"%lc",WALL_CLOTH);
	//下
	SetPos(0, lines - 1);
	for (i = 0; i < cols; i += 2)
		wprintf(L"%lc", WALL_CLOTH);
	//左
	for (i = 0; i < lines; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL_CLOTH);
	}
	//右
	for (i = 0; i < lines; i++)
	{
		SetPos(cols, i);
		wprintf(L"%lc", WALL_CLOTH);
	}
}

//创建蛇的节点以及头插
void SetNode(pSnakeNode* ppsnode, int x, int y )//我们要修改头结点地址，所以要传二级指针
{
	assert(ppsnode);
	pSnakeNode newnode = NULL;
	newnode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (NULL == newnode)
	{
		perror("SetNode-->malloc");
		exit(-1);
	}
	
	newnode->_x = x;
	newnode->_y = y;
	newnode->_next = *ppsnode;
	*ppsnode = newnode;
}

//蛇的初始化
void InitSnake(pSnake ps,int cols,int lines)
{
	assert(ps);
	//初始化部分
	//创建蛇节点
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		SetNode(&(ps->_psnakenode), cols + 2 * i, lines);
	}

	ps->_dir = right;
	ps->_score = 0;
	ps->_status = noroperate;
	ps->_sleep_time = 200;
	ps->_a_food_score = 10;
}

//将蛇打印在棋盘上
void SnakePrint(pSnakeNode psnode)
{
	assert(psnode);
	pSnakeNode pcur = psnode;
	while (pcur != NULL)
	{
		SetPos(pcur->_x, pcur->_y);
		wprintf(L"%lc", SNAKE1_CLOTH);
		pcur = pcur->_next;
	}
}

//创建食物
void CreatFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	again:
	do
	{
		x = rand() % (MAP_COLS - 3) + 2;
		y = rand() % (MAP_LINES - 2) + 1;
	} while (x % 2 != 0);

	pSnakeNode pcur = ps->_psnakenode;
	while (pcur)
	{
		if ((pcur->_x == x) && (pcur->_y))
		{
			goto again;
		}
		pcur = pcur->_next;
	}

	SetNode(&(ps->_pfood), x, y);
	SetPos(ps->_pfood->_x, ps->_pfood->_y);
	wprintf(L"%lc", FOOD_CLOTH);
}

//打印帮助信息
void PrintHelpInfo()
{
	//固定信息
	SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 - 2);
	wprintf(L"%ls",L"不能穿墙，不能咬到自己");
	SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 - 1);
	wprintf(L"用↑↓→←来控制蛇的移动，F1为加速，F2为减速");
	SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 + 1);
	wprintf(L"%ls", L"ESC退出 <空格>暂停");
	SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 + 2);
	wprintf(L"%ls", L"加速可获得更高的得分");
}

//判断新建蛇头的位置与墙、蛇身、食物的关系
void CheckSnakeNode(pSnake ps)
{
	//撞墙
	if (((ps->_psnakenode->_x) > MAP_COLS - 2) || ((ps->_psnakenode->_x) < 1) ||
		((ps->_psnakenode->_y) > MAP_LINES - 1) || ((ps->_psnakenode->_y) < 1))
	{
		ps->_status = kill_by_wall;
		return;
	}
	
	//撞食物
	if ((ps->_psnakenode->_x == ps->_pfood->_x) && (ps->_psnakenode->_y == ps->_pfood->_y))
	{
		ps->_status = meet_food;
		return;
	}

	//撞蛇身
	pSnakeNode pcur = ps->_psnakenode->_next;
	while (pcur)
	{
		if ((ps->_psnakenode->_x == pcur->_x) && (ps->_psnakenode->_y == pcur->_y))
		{
			 ps->_status = kill_by_snake;
			 return;
		}
		pcur = pcur->_next;
	}
	
}

//蛇移动一次
void SnakeMove(pSnake ps)
{
	//根据蛇的方向决定下一步
	switch (ps->_dir)
	{
	case up:
		SetNode(&(ps->_psnakenode),ps->_psnakenode->_x,ps->_psnakenode->_y - 1);
		break;
	case down:
		SetNode(&(ps->_psnakenode), ps->_psnakenode->_x, ps->_psnakenode->_y + 1);
		break;
	case left:
		SetNode(&(ps->_psnakenode), ps->_psnakenode->_x - 2, ps->_psnakenode->_y);
		break;
	case right:
		SetNode(&(ps->_psnakenode), ps->_psnakenode->_x + 2, ps->_psnakenode->_y);	
		break;
	}

	pSnakeNode ptial = NULL;
	//判断新建蛇头的位置与墙、蛇身、食物的关系,并根据判断结果执行下一步操作
	CheckSnakeNode(ps);
	switch (ps->_status)
	{
	case noroperate:
		//清除在屏幕上的蛇尾节点并去除蛇的最后一个节点-->蛇的移动   
		ptial = ps->_psnakenode;
		while (ptial->_next->_next!= NULL)
		{
			ptial = ptial->_next;
		}
		SetPos(ptial->_next->_x, ptial->_next->_y);
		printf("  ");
		free(ptial->_next->_next);
		ptial->_next = NULL;

		//打印蛇
		SnakePrint(ps->_psnakenode, SNAKE1_CLOTH);

		break;
	case meet_food:

		//加分
		ps->_score += ps->_a_food_score;
		SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 - 5);
		wprintf(L"分数：%d 当前每个食物得分：%d   ",ps->_score,ps->_a_food_score);
		//打印蛇
		SnakePrint(ps->_psnakenode, SNAKE1_CLOTH);
		//重新创建食物
		free(ps->_pfood);
		ps->_pfood = NULL;
		CreatFood(ps);
		break;
	case kill_by_snake:

		//游戏结束
		Game_Over();

		break;
	case kill_by_wall:

		//游戏结束
		Game_Over();

		break;
	}
}


/***********************************************************************************************/
/*********************************游戏功能实现函数--直接实现************************************/
/***********************************************************************************************/

//游戏开始
void Game_Start(pSnake psnake1)
{
	assert(psnake1);
	//游戏环境设置
	GameSet();

	//游戏开始界面
	Game_Star_Screen();

	//游戏介绍（模式选择）界面
 	Game_Introduction();

	//创建地图
	CreatMap(MAP_COLS, MAP_LINES); 

	//创建食物
	CreatFood(psnake1);

	//初始化蛇 打印初始化的蛇
	InitSnake(psnake1, INIT_COLS1, INIT_LINES1);
	SnakePrint(psnake1->_psnakenode, SNAKE1_CLOTH);

}

//游戏运行逻辑
void Game_Run(pSnake psnake1)
{
	//打印帮助信息
	PrintHelpInfo();
	SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 - 5);
	wprintf(L"分数：%d 当前每个食物得分：%d", psnake1->_score, psnake1->_a_food_score);
	//蛇的状态判断
	do
	{
		psnake1->_status = noroperate;//重新设置状态
		if (KEY_PASS(VK_UP) && psnake1->_dir != down)
		{
			psnake1->_dir = up;
		}
		else if (KEY_PASS(VK_DOWN) && psnake1->_dir != up)
		{
			psnake1->_dir = down;
		}
		else if (KEY_PASS(VK_LEFT) && psnake1->_dir != right)
		{
			psnake1->_dir = left;
		}
		else if (KEY_PASS(VK_RIGHT) && psnake1->_dir != left)
		{
			psnake1->_dir = right;
		}
		else if (KEY_PASS(VK_F1))
		{
			//加速
			if (psnake1->_sleep_time > 80)
			{
				psnake1->_sleep_time -= 30;
				psnake1->_a_food_score += 2;
			}
			SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 - 5);
			wprintf(L"分数：%d 当前每个食物得分：%d   ", psnake1->_score, psnake1->_a_food_score);
		}
		else if (KEY_PASS(VK_F2))
		{
			//减速
			if (psnake1->_a_food_score > 4)
			{
				psnake1->_sleep_time += 30;
				psnake1->_a_food_score -= 2;
			}
			SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 - 5);
			wprintf(L"分数：%d 当前每个食物得分：%d   ", psnake1->_score, psnake1->_a_food_score);
		}

		if (KEY_PASS(VK_SPACE))
			{
			SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 + 3);
			system("pause");
			SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 + 3);
			printf("                    ");
			}
		else if (KEY_PASS(VK_ESCAPE))
			{
			psnake1->_status = norend;
			SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 + 3);

			wprintf(L"退出游戏！");
			Game_Over();
			}

		//蛇移动一次
		SnakeMove(psnake1);

		//休眠时间-->控制蛇的速度
		Sleep(psnake1->_sleep_time);
	} while (psnake1->_status == noroperate||psnake1->_status==meet_food);

}

//游戏结束
void Game_Over()
{
	SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 + 1);
	wprintf(L"%ls", L"                    ");
	SetPos(MAP_COLS + 10, CONCOLE_LINES / 2 + 1);
	wprintf(L"%ls", L"游戏结束");
	SetPos(0, CONCOLE_LINES - 5);
	exit(1);
}